/**
 * 
 */
package steffen.java.utils.menu;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
import java.util.TreeMap;

import steffen.java.utils.menu.format.DefaultPrintFormatter;
import steffen.java.utils.menu.format.PrintFormatter;
import steffen.java.utils.menu.option.MenuOption;
import steffen.java.utils.menu.option.PredefinedOption;
import steffen.java.utils.menu.reflection.ReflectionMenuOption;
import steffen.java.utils.results.listener.MenuItem;
import steffen.java.utils.results.listener.ResultListener;

/**
 * @author Steffen Baumann
 * @version 1.0
 * 
 */
public class DefaultMenu<T> {

	private String startMessage = "";
	private String endMessage = "";
	
	private TreeMap<String, Object> numberValueMap = new TreeMap<String, Object>();
	private TreeMap<String, MenuOption> commandValueMap = new TreeMap<String, MenuOption>();
	
	private List<PredefinedOption> predefinedValues = Arrays.asList(PredefinedOption.getHelpOption(), PredefinedOption.getCancelOption(), PredefinedOption.getExitOption());
	
	private static final String ERROR_MESSAGE_RANGE = "Your choosen value is not provided by the menu. Repeat your input: ";
	
	private ReflectionMethodCaller reflectionMethodCaller = null;
	
	public static final int DEFAULT = 0x0000;
	public static final int USER = 0x0001;
	public static final int NONE = 0x0003;
	public static final int BOTH =  0x0002;
	public static final int REFLECTION = 0x0004;
	
	private List<T> items = new ArrayList<>();

	private List<MenuOption> userDefinedValues = new ArrayList<MenuOption>();
	private List<ReflectionMenuOption> reflectionValues = new ArrayList<ReflectionMenuOption>();
	
	private PrintFormatter<T> defaultFormatter = new DefaultPrintFormatter<T>();
	private PrintFormatter<MenuOption> predefinedOptionFormatter = new PrintFormatter<MenuOption>() {

		@Override
		public String formatMenuNumber(int index) {
			return defaultFormatter.formatMenuNumber(index);
		}

		@Override
		public String formatMenuItem(MenuOption item, int index) {
			return "["+item.getOptionName()+"]";
		}

		@Override
		public String formatSpace(int index) {
			return defaultFormatter.formatSpace(index);
		}
	};
	private ResultListener<T> listener = null;

	public DefaultMenu(){
		this(new ArrayList<T>());
	}
	
	public DefaultMenu(T[] items) {
		this(Arrays.asList(items));
	}

	public DefaultMenu(List<T> items) {
		this.items =items;
		storeItems(items);
	}
	/**
	 * @return the formatter
	 */
	public PrintFormatter<T> getFormatter() {
		return defaultFormatter;
	}

	/**
	 * @param formatter
	 *            the formatter to set
	 */
	public void setFormatter(PrintFormatter<T> formatter) {
		this.defaultFormatter = formatter;
		storeItems(items);
	}

	public void setResultListener(ResultListener<T> resultListener) {
		this.listener = resultListener;
	}

	public ResultListener<T> getResultListener() {
		return this.listener;
	}

	/**
	 * @return the userDefinedValues
	 */
	public List<MenuOption> getUserDefinedValues() {
		return userDefinedValues;
	}

	/**
	 * @param userDefinedValues the userDefinedValues to set
	 */
	public void setUserDefinedValues(List<MenuOption> userDefinedValues) {
		this.userDefinedValues = userDefinedValues;
	}

	/**
	 * @return the reflectionValues
	 */
	public List<ReflectionMenuOption> getReflectionValues() {
		return reflectionValues;
	}

	/**
	 * @param reflectionValues the reflectionValues to set
	 */
	public void setReflectionValues(List<ReflectionMenuOption> reflectionValues) {
		this.reflectionValues = reflectionValues;
	}

	/**
	 * @return the reflectionMethodCaller
	 */
	public ReflectionMethodCaller getReflectionMethodCaller() {
		return reflectionMethodCaller;
	}

	/**
	 * @param reflectionMethodCaller the reflectionMethodCaller to set
	 */
	public void setReflectionMethodCaller(
			ReflectionMethodCaller reflectionMethodCaller) {
		this.reflectionMethodCaller = reflectionMethodCaller;
	}

	public void setDefaultOptions(int type) {
		numberValueMap = new TreeMap<>();
		storeItems(items);
		switch (type) {
		case DEFAULT:
			addPredefinedOptions(numberValueMap.size());
			break;
		case USER:
			addUserDefinedOptions(numberValueMap.size());
			break;
		case BOTH:	
			addUserDefinedOptions(numberValueMap.size());
			addPredefinedOptions(numberValueMap.size());
		case NONE:
			break;
		case REFLECTION:
			addReflectionOptions(numberValueMap.size());
			break;
		default:
			throw new IllegalArgumentException("The type" + type
					+ "is unknown. DefaultValues are turned off.");
		}
	}

	private void addPredefinedOptions(int index) {
		for(int i = 0; i < predefinedValues.size(); i++){
			
			String formattedKey = defaultFormatter.formatMenuNumber(i+index);
			numberValueMap.put(formattedKey, predefinedValues.get(i));
			String command = predefinedValues.get(i).getCommand().toLowerCase();
			commandValueMap.put(command, predefinedValues.get(i));
		}
	}

	private void addUserDefinedOptions(int index) {
		for(int i = 0; i < userDefinedValues.size(); i++){
			String formattedKey = defaultFormatter.formatMenuNumber(i+index);
			numberValueMap.put(formattedKey, userDefinedValues.get(i));
			String command = userDefinedValues.get(i).getCommand();
			if(command != null && !command.isEmpty()){
				command = command.toLowerCase();
				commandValueMap.put(command, userDefinedValues.get(i));
			}
		}
	}
	
	private void addReflectionOptions(int index) {
		for(int i = 0; i < reflectionValues.size(); i++){
			String formattedKey = defaultFormatter.formatMenuNumber(i+index);
			numberValueMap.put(formattedKey, reflectionValues.get(i));
			String command = reflectionValues.get(i).getCommand();
			if(command != null && !command.isEmpty()){
				command = command.toLowerCase();
				commandValueMap.put(command, reflectionValues.get(i));
			}
		}
	}

	private void storeItems(List<T> items) {
		for (int i = 0; i < items.size(); i++) {
			String formattedKey = defaultFormatter.formatMenuNumber(i);
			numberValueMap.put(formattedKey, items.get(i));
		}
	}

	@SuppressWarnings("unchecked")
	public void printValues() {
		System.out.println(startMessage);
		int index = 0;
		for(String key: numberValueMap.keySet()){
			Object obj = numberValueMap.get(key);
			if(obj instanceof MenuOption){
				System.out.print(key);
				System.out.print(defaultFormatter.formatSpace(index));
				System.out.println(predefinedOptionFormatter.formatMenuItem((MenuOption)obj, index));
			}else{
				System.out.print(defaultFormatter.formatMenuNumber(index));
				System.out.print(defaultFormatter.formatSpace(index));
				System.out.println(defaultFormatter.formatMenuItem((T)obj, index));
			}
			index ++;
		}
		System.out.println(endMessage);
	}

	@SuppressWarnings("unchecked")
	public void selectItem(){
		String value = "";
		Scanner scan = new Scanner(System.in);
		do{
			value = scan.nextLine();
		}while(!isInputCorrect(value));
		scan.close();
		if(listener != null){
			Object obj = null;
			if(isCommand(value)){
				obj = commandValueMap.get(value.toLowerCase());
			}else if(isMenuKey(value)){
				obj = numberValueMap.get(value);
			}
			
			if(isMenuOption(obj)){
				if(isReflectionOption(obj)){
					if(reflectionMethodCaller != null){
						ReflectionMenuOption option = (ReflectionMenuOption)obj;
						reflectionMethodCaller.callMethod(option.getMethodName(), option.getMethodParameters());
					}
				}else if(isPredefinedOption(obj)){
					listener.choosenPredifendOption((PredefinedOption) obj);
				}else{
					listener.choosenUserdefinedOption((MenuOption) obj);
				}
			}else{
				int index = items.indexOf(obj);
				listener.choosenValue(new MenuItem<T>(index,(T)obj));
			}
		}
	}
	
	private boolean isReflectionOption(Object obj) {
		return (obj instanceof ReflectionMenuOption);
	}

	private boolean isInputCorrect(String value){
		String errorMessage = "";
		if(isCommand(value)){
			return true;
		}else {
			errorMessage = "blablubb";
		}
		if(isMenuKey(value)){
			return true;
		}else{
			errorMessage = ERROR_MESSAGE_RANGE;
		}
//		if(isValidNumber(value)){
//			if(isMenuKey(value)){
//				return true;
//			}else{
//				errorMessage = ERROR_MESSAGE_RANGE;
//			}
//		}else{
//			errorMessage = ERROR_MESSAGE_NUMBER;
//		}
		
		System.out.println(errorMessage);
		return false;
	}
	
	private boolean isCommand(String value) {
		if(commandValueMap.containsKey(value.toLowerCase())){
			return true;
		}else{
			return false;
		}
	}

	private boolean isPredefinedOption(Object obj) {
		return (obj instanceof PredefinedOption);
	}

	private boolean isMenuOption(Object obj){
		return (obj instanceof MenuOption);
	}
	
	private boolean isMenuKey(String value) {
//		int choise = Integer.parseInt(value);
		if(numberValueMap.containsKey(value)){
			return true;
		}else{
			return false;
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DefaultMenu<String> menu = new DefaultMenu<>(new String[] { "Hallo",
				"Test", "Beispiel" });
		menu.printValues();

		DefaultMenu<Date> menu2 = new DefaultMenu<>(new Date[] { new Date(),
				new Date(), new Date() });
		menu2.printValues();

		PrintFormatter<Date> dateFormatter = new PrintFormatter<Date>() {

			@Override
			public String formatMenuNumber(int index) {
				return index+"";
			}

			@Override
			public String formatMenuItem(Date item, int index) {
				return "" + item.getTime();
			}

			@Override
			public String formatSpace(int index) {
				return " - ";
			}
		};
		menu2.setFormatter(dateFormatter);
//		List<String> userDefined = Arrays.asList("Test","Test2","Test3");
//		menu2.setUserDefinedValues(userDefined);
//		menu2.setDefaultValues(USER);
		menu2.printValues();
	}
}
